//-----------------------------------------------------------------------
// <copyright company="Olson Software Limited">
//      (c) Copyright Olson Software Limited.
//      This source is subject to the Microsoft Public License (Ms-PL).
//      Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
//      All other rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Navigation;

namespace BHB.Library.Navigation
{
    /// <summary>
    /// Custom URI mapper provides support for custom URI mapping using code, rather than just using regular expressions, 
    /// which is the only support provided by the Microsoft URI mapper.  it also provides support for canceling navigation
    /// if you wish to display a dialog when a URI is specified
    /// </summary>
    /// <example>
    /// There are two parts to using the custom URI mapper. Firstly, you declare the URI mapper and app.xaml as you would the
    /// normal Microsoft mapper. If you wish to be able to cancel navigation, you can set a default page, in app.xaml. Secondly, 
    /// if you wish to be able to cancel navigation to display a dialog or anything else, you need to set the frame.
    /// 
    /// In app.xaml:
    /// <code>
    /// <routing:CustomUriMapper x:Key="uriMapper" DefaultPage="/Views/EmptyPage.xaml">
    //      <Navigation:UriMapping Uri="search/{searchfor}" MappedUri="/pages/searchpage.xaml?searchfor={searchfor}"/>
    //  </routing:CustomUriMapper>
    /// </code>
    /// 
    /// To set the frame, probably in the main window.
    /// <code>
    /// App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
    /// </code>
    /// 
    /// </example>
    [ContentProperty("UriMappings")]
    public class CustomUriMapper : UriMapperBase
    {
        // private variables
        private bool _cancelNavigation;

        /// <summary>
        /// Gets or sets a list of UriMapping objects.
        /// </summary>
        public Collection<UriMapping> UriMappings { get; private set; }
        /// <summary>
        /// Gets or sets a list of CustomUriMappings objects.
        /// </summary>
        public Collection<CustomUriMapping> CustomUriMappings { get; private set; }

        /// <summary>
        /// specifies the default page. This is required because even when canceling navigation, the Microsoft URI mapper
        /// needs to provide a page to map to. To use this create a blank page and reference it.
        /// </summary>
        public Uri DefaultPage { get; set; }

        private Frame _frame;
        /// <summary>
        /// specifies the frame in which navigation for this URI mapper will occur. If you want to be able to cancel 
        /// navigation, this frame needs to be set.
        /// </summary>
        /// <example>
        /// To set the frame, probably in the main window.
        /// <code>
        /// (App.Current.Resources["uriMapper"] as CustomUriMapper).Frame = frame;
        /// </code>
        /// </example>
        public Frame Frame
        {
            get { return _frame; }
            set
            {
                _frame = value;
                _frame.Navigating += FrameNavigating;
            }
        }

        

        public CustomUriMapper()
        {
            UriMappings = new Collection<UriMapping>();
            CustomUriMappings = new Collection<CustomUriMapping>();
        }
        
        /// <summary>
        /// Maps a given URI and returns a mapped URI.
        /// </summary>
        /// <param name="uri">Original URI value to be mapped to a new URI.</param>
        /// <returns>A URI derived from the <paramref name="uri"/> parameter.</returns>
        public override Uri MapUri(Uri uri)
        {
            _cancelNavigation = false;

            Collection<UriMapping> uriMappings = UriMappings;
            if (uriMappings == null)
            {
                throw new InvalidOperationException("MustNotHaveANullUriMappingCollection");
            }
            foreach (UriMapping mapping in uriMappings)
            {
                Uri uri2 = mapping.MapUri(uri);
                if (uri2 != null)
                {
                    return CheckCanNavigateToUri(uri, uri2);
                }
            }

            foreach (CustomUriMapping mapping in CustomUriMappings)
            {
                Uri uri2;
                if (!mapping.MapUri(uri, out uri2)) continue;
                if (uri2 == null && Frame!=null)
                {
                    return CancelNavigation();
                }
                return CheckCanNavigateToUri(uri, uri2);
            }
            // now nothing...
            return CheckCanNavigateToUri(uri, uri); 
        }

        /// <summary>
        /// cancels navigation to the current URI.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// thrown if there is no default page
        /// </exception>
        /// <returns>the new URI to navigate to, which will be the default page. This is required by 
        /// the Microsoft navigation system</returns>
        protected Uri CancelNavigation()
        {
            // if there is no URI to go to, we will cancel the navigation at the next possible opportunity
            _cancelNavigation = true;
            if (DefaultPage==null)
            {
                throw new InvalidOperationException("must have a default page");
            }
            // we need to return a valid URI, that points to the actual page, because the cancel is not 
            // checked until after the validity of the URI is checked
            return DefaultPage;
        }

        /// <summary>
        /// this function is called before any URI is returned. This gives you the opportunity to do any custom work, 
        /// such as authentication, prior to the URI being returned to the navigation system
        /// </summary>
        /// <param name="unmappedUri">the URI that was provided by the navigation system, before it was mapped</param>
        /// <param name="mappedUri">the URI, after mapping</param>
        /// <returns>the URI to navigate to, by default this is the same as the mapped URI</returns>
        protected virtual Uri CheckCanNavigateToUri(Uri unmappedUri, Uri mappedUri)
        {
            return mappedUri;
        }

        void FrameNavigating(object sender, NavigatingCancelEventArgs e)
        {
            e.Cancel = _cancelNavigation;
        }
    }
}